﻿//  Copyright (c) 2011 Ray Liang (http://www.dotnetage.com)
//  Licensed MIT: http://www.opensource.org/licenses/mit-license.php

using System;
using System.Collections.Generic;
using System.Linq;

namespace DNA.Mvc.Data.Entity
{
    public class WidgetRepository : EntityRepository<WidgetInstance>, IWidgetRepository
    {
        public WidgetRepository() : base() { }

        public WidgetRepository(DB dbContext) : base(dbContext) { }

        /// <summary>
        /// Create the Widget Instance by specified widget descriptor ,the web page visual path ,the zone element id and the position in widgets sequence.
        /// </summary>
        /// <param name="id">The new widget id.</param>
        ///<param name="descriptor">The WidgetDescriptor instance.</param>
        /// <param name="webPagePath">Specified the web page path to added</param>
        /// <param name="zoneID">The zone element id</param>
        /// <param name="position"> The position in widgets sequence</param>
        /// <returns>A new Widget instance.</returns>
        public virtual WidgetInstance AddWidget(Guid id, WidgetDescriptor descriptor, string webPagePath, string zoneID, int position)
        {
            int pos = 0;
            if (position > -1)
                pos = position;

            if (descriptor == null)
                throw new ArgumentNullException("descriptor");

            if (string.IsNullOrEmpty(webPagePath))
                throw new ArgumentNullException(webPagePath);

            if (string.IsNullOrEmpty(zoneID))
                throw new ArgumentNullException(zoneID);

            WebPage page = Context.WebPages.FirstOrDefault(p => p.Path.Equals(webPagePath.ToLower()));

            if (page == null)
                throw new WebPageNotFoundException(webPagePath);

            //Update the position below pos
            var widgetsForUpdate = DbSet.Where(w => w.PageID == page.ID && (w.ZoneID == zoneID) && (w.Pos >= pos));

            if (widgetsForUpdate != null)
            {
                foreach (WidgetInstance _w in widgetsForUpdate)
                    _w.Pos++;
                if (IsOwnContext)
                    Context.SaveChanges();
            }

            WidgetInstance widget = new WidgetInstance()
            {
                ID = id,
                IsExpanded = true,
                Pos = pos,
                ZoneID = zoneID
            };

            widget.From(descriptor);
            widget.WidgetDescriptor = descriptor;
            widget.WebPage = page;

            Create(widget);
            return widget;
        }

        /// <summary>
        /// Create the Widget instance by specified widget template.
        /// </summary>
        /// <param name="id">The new widget id.</param>
        /// <param name="tmpl">The widget template object.</param>
        /// <param name="webPagePath">The virutal path of the web page which the widget add to.</param>
        /// <param name="zoneID">The widget zone id that contains the new widget.The Widget will be added to zone0 when this paramater set to null </param>
        /// <param name="position">The widget position.</param>
        /// <returns>Returns a new widget instance.</returns>
        public virtual WidgetInstance AddWidget(Guid id, WidgetTemplate tmpl, string webPagePath, string zoneID = null, int position = 0)
        {
            var descriptor = Context.WidgetDescriptors.FirstOrDefault(w => w.InstalledPath.Equals(tmpl.ID, StringComparison.OrdinalIgnoreCase));
           
            if (descriptor == null)
                throw new Exception(string.Format("Widget template \"{0}\" not registered",tmpl.Name));

            var _zoneID = "zone0";
            
            if (string.IsNullOrEmpty(zoneID))
            {
                if (!string.IsNullOrEmpty(tmpl.Defaults.ZoneID))
                    _zoneID = tmpl.Defaults.ZoneID;
            }

            var widget = AddWidget(id, descriptor, webPagePath, _zoneID, position);
            widget.Title = tmpl.Defaults.Title;
            widget.TitleLinkUrl = tmpl.Defaults.TitleLinkUrl;
            widget.IconUrl = tmpl.Defaults.IconUrl;
            widget.ShowBorder = tmpl.Defaults.ShowBorder;
            widget.ShowHeader = tmpl.Defaults.ShowHeader;
            widget.HeaderCssText = tmpl.Defaults.HeaderCssText;
            widget.CssText = tmpl.Defaults.CssText;
            widget.BodyCssText = tmpl.Defaults.BodyCssText;

            if (string.IsNullOrEmpty(widget.IconUrl) && !string.IsNullOrEmpty(descriptor.IconUrl))
                widget.IconUrl = descriptor.IconUrl;

            if (tmpl.UserPreferences != null)
            {
                var dict = new Dictionary<string, object>();
                foreach (var preference in tmpl.UserPreferences)
                {
                    var type = Type.GetType(preference.Type);
                    dict.Add(preference.Name, Convert.ChangeType(preference.Value, type));
                }
                if (dict.Count > 0)
                    widget.SaveUserPreferences(dict);
            }

            if (IsOwnContext)
                Context.SaveChanges();

            return widget;
        }

        /// <summary>
        /// Move the specified widget to a zone.
        /// </summary>
        /// <param name="id">The widget instance id.</param>
        /// <param name="zoneID">the zone element id</param>
        /// <param name="position">The position in widgets sequence</param>
        public virtual void MoveTo(Guid id, string zoneID, int position)
        {
            var target = Find(id);

            if (target == null)
                throw new Exception("Widget not found.");

            var _pageID = target.PageID;

            var widgets = DbSet.Where(w => w.PageID == _pageID);

            var seqCollection = widgets.Where(w=>w.ZoneID.Equals(zoneID,StringComparison.OrdinalIgnoreCase))
                                                .OrderBy(p => p.Pos)
                                                .Select(p => p.Pos);

            if (seqCollection.Count() == 0)
            {
                target.Pos = 0;
                target.ZoneID = zoneID;
                if (IsOwnContext)
                    Context.SaveChanges();
                return;
            }

            int upperBound = seqCollection.Max();
            int lowerBound = seqCollection.Min();

            int _from = target.Pos;
            int _to = position;

            if (_to > upperBound)
                _to = upperBound;
            else
            {
                if (_to < lowerBound)
                    _to = lowerBound;
            }

            //1.Move up
            if (_from > _to)
            {
                widgets.Where(w => w.ZoneID.Equals(zoneID, StringComparison.OrdinalIgnoreCase) && w.Pos >= _to && w.Pos < _from)
                    .OrderBy(p => position)
                    .AsParallel()
                    .ForAll(p => p.Pos++);

            }

            //2.Move down
            if (_from < _to)
            {
                widgets.Where(w => w.ZoneID.Equals(zoneID, StringComparison.OrdinalIgnoreCase) && w.Pos > _from && w.Pos <= _to)
                   .OrderBy(p => position)
                   .AsParallel()
                   .ForAll(p => p.Pos--);
            }

            target.Pos = _to;
            target.ZoneID = zoneID;

            if (IsOwnContext) 
                Context.SaveChanges();
           
        }
    }
}
